/*
 * A simple encapsulation of SDL's audio module.
 *
 * Please look `tests/testAudio.cpp` to find usage.
 */
#include "audio.hpp"

namespace sdl_engine {

/*****************
 * AudioConfig
*****************/

AudioConfig::AudioConfig() {
    spec_.channels = STEREO;
    spec_.format = AUDIO_F32;
    spec_.freq = 48000;
    spec_.samples = 4096;
    spec_.callback = nullptr;
    spec_.userdata = nullptr;
}

AudioConfig::AudioConfig(const SDL_AudioSpec& spec): spec_(spec) {}

std::ostream& operator<<(std::ostream& o, const AudioConfig& config) {
    o << "AudioDevice(" << "channels = " << static_cast<int>(config.spec_.channels) << std::endl
                        << "\tformat = " << config.spec_.format << std::endl
                        << "\tfreq = " << config.spec_.freq << std::endl
                        << "\tsamples = " << config.spec_.samples << std::endl
                        << "\tuserdata = " << (config.spec_.userdata ? config.spec_.userdata : 0) << ")";
    return o;
}

/*****************
 * AudioDevice
*****************/

AudioDevice::AudioDevice(bool playbackAudio,
                         AudioCallback cb,
                         void* userdata) {
    AudioConfig config;
    config.SetCallback(cb);
    config.SetUserdata(userdata);
    if (playbackAudio) {
        if (SDL_OpenAudio(&config.GetRaw(), nullptr) < 0) {
            Loge(Fmt("open default playback audio failed: %s", SDL_GetError()));
        } else {
            id_ = 1;
            config_ = config;
        }
    } else {
        openAudioDevice(config, playbackAudio, "");
    }
}

AudioDevice::AudioDevice(AudioConfig config, bool playbackAudio) {
    if (playbackAudio) {
        openDefaultAudio(config);
    } else {
        openAudioDevice(config, playbackAudio, "");
    }
}

void AudioDevice::openDefaultAudio(AudioConfig& config) {
    if (SDL_OpenAudio(&config.GetRaw(), nullptr) < 0) {
        Loge(Fmt("open default playback audio failed: %s", SDL_GetError()));
    } else {
        id_ = 1;
        config_ = config;
    }
}

void AudioDevice::openAudioDevice(AudioConfig& config,
                                  bool playback,
                                  const std::string& name) {
    // FIXME is there any way to move this version check to compile time?
    SDL_version version;
    SDL_VERSION(&version);
    if (version.patch < 5 && !playback) {
        Loge("SDL version < 2.0.5 don't support record device");
    } else {
        if (name.empty()) {
            id_ = SDL_OpenAudioDevice(nullptr,
                                      playback? SDL_FALSE : SDL_TRUE,
                                      &config.GetRaw(),
                                      nullptr,
                                      0);
        } else {
            id_ = SDL_OpenAudioDevice(name.c_str(),
                                      playback? SDL_FALSE: SDL_TRUE,
                                      &config.GetRaw(),
                                      nullptr,
                                      0);
        }
        if (id_ <= 0) {
            Logw(Fmt("open audio failed: %s", SDL_GetError()));
        }
    }
}

AudioDevice::AudioDevice(const std::string& name,
                         AudioConfig config,
                         bool playbackAudio): config_(config) {
    openAudioDevice(config, playbackAudio, name);
}

void AudioDevice::Start() {
    if (id_ <= 0) {
        Loge("audio start failed! Invalid ID");
    } else if (id_ == 1) {
        SDL_PauseAudio(0);
    } else {
        SDL_PauseAudioDevice(id_, 0);
    }
}

void AudioDevice::Stop() {
    if (id_ <= 0) {
        Loge("audio start failed! Invalid ID");
    } else if (id_ == 1) {
        SDL_PauseAudio(1);
    } else {
        SDL_PauseAudioDevice(id_, 1);
    }
}

AudioDevice::~AudioDevice() {
    if (id_ >= 2) {
        SDL_CloseAudioDevice(id_);
    } else if (id_ == 1) {
        SDL_CloseAudio();
    }
}


/*****************
 * AudioSample
*****************/

AudioSample::AudioSample(const std::string& filename) {
    u32 size = 0;
    SDL_AudioSpec spec;
    if (SDL_LoadWAV(filename.c_str(), &spec, &data_, &size) == nullptr) {
        Logw(Fmt("load audio %s failed: %s",
                 filename.c_str(), SDL_GetError()));
    } else {
        size_ = size;
        config_ = AudioConfig(spec);
    }
}

AudioSample::AudioSample(u8* data, u32 size, const AudioConfig& config)
    : data_(data), size_(size), config_(config) {}

AudioSample::~AudioSample() {
    SDL_FreeWAV(data_);
}

}
